/**
 * @file SDCard.c
 * @author EdJ
 * @brief SDCard文件操作
 *          写入调用顺序:
 *              SDCard_File_Open()
 *              SDCard_File_Write_Img()
 *              SDCard_File_Close()
 *          读取调用顺序:
 *              SDCard_File_Open()
 *              SDCard_File_Read_Img()
 *              // ePaper_SetImgDataByChar()
 *              SDCard_File_Close()
 * @version 0.1
 * @date 2024-01-13
 * 
 * @copyright Copyright (c) 2024
 * 
 */





/************************************************************************
 * INCLUDE
 ************************************************************************/
#include "esp_log.h"
#include "esp_err.h"
#include "esp_random.h"
// for SD Card
#include <string.h>
#include <sys/unistd.h>
#include <sys/stat.h>
// #include "fatfs/vfs/esp_vfs_fat.h"
// #include "sdmmc/include/sdmmc_cmd.h"
#include "esp_vfs_fat.h"
#include "esp_vfs.h"
#include "sdmmc_cmd.h"
#include <dirent.h>
//
#include "../main.h"
//


/************************************************************************
 * DEFINES
 ************************************************************************/
#define TAG_SD          "SD Card"

#define SD_PIN_CS       9



/************************************************************************
 * VARIABLE
 ************************************************************************/
static spi_host_device_t SPIHostID; //SPI bus
static sdmmc_card_t *SDCard; //a SD Card, it keeps SD/MMC card information
//
const char MountPoint[] = MOUNT_POINT;
static FILE *File = NULL; //打开的文件描述符
static uint16_t DirFileNum = 0; //保存指定目录下文件数量
static char DirFileNames[FILE_NUM_MAX][FILE_NAME_LEN_MAX] = {'\0'}; //指定目录下文件名
static uint8_t Pages = 0; //页数, 每页长度为FILE_NUM_IN_PAGE
static uint8_t LastPage = 0; //尾页文件数量
int8_t CurrentIdx = 0; //当前的位置      当前页中位置
int8_t CurrentPage = 0; //当前页数



/************************************************************************
 * FUNCTIONS
 ************************************************************************/
esp_err_t SDCard_Init(spi_host_device_t host_id)
{
    esp_err_t ret;

    SPIHostID = host_id;

    //--------- SD mount config
    esp_vfs_fat_sdmmc_mount_config_t sd_mount_cfg = {
        .format_if_mount_failed = false, //mount失败时不进行format
        .allocation_unit_size = 16 * 1024, //若进行format则使用参数.allocation_unit_size
        .max_files = 5, //同时打开文件数量
    };
    //--------- SD host setting
    sdmmc_host_t sd_host = {
        .flags = SDMMC_HOST_FLAG_SPI | SDMMC_HOST_FLAG_DEINIT_ARG,
        .slot = SPIHostID, //=VSPI_HOST; SDSPI_DEFAULT_HOST=HSPI_HOST=SPI2_HOST
        .max_freq_khz = SDMMC_FREQ_DEFAULT,
        .io_voltage = 3.3f,
        .init = &sdspi_host_init, //实际为空
        .set_bus_width = NULL, //func to set bus width
        .get_bus_width = NULL, //func to get bus width
        .set_bus_ddr_mode = NULL, //func to set DDR mode
        .set_card_clk = &sdspi_host_set_card_clk, //会配置freq/CS/SPIMode/queue_size, 并在spi bus上挂载device
        .do_transaction = &sdspi_host_do_transaction, //SD数据传输
        .deinit_p = &sdspi_host_remove_device, //通过sdspi_dev_handle_t拿到slot_info_t, 并置NULL
        .io_int_enable = &sdspi_host_io_int_enable, //实际为空
        .io_int_wait = &sdspi_host_io_int_wait, //？？？含有 semaphore 操作
        .command_timeout_ms = 0,
    }; //SD/MMC description; 可使用SDSPI_HOST_DEFAULT()进行默认配置

    //--------- SPI Device config - add SD as SPI device
    sdspi_device_config_t sd_dev_cfg = {
        .host_id = sd_host.slot, //本SD device所在的SPI总线
        .gpio_cs = SD_PIN_CS, //本SD device所使用片选
        .gpio_cd = SDSPI_SLOT_NO_CD, //card detect(CD)
        .gpio_wp = SDSPI_SLOT_NO_WP, //write protect(WP)
        .gpio_int = GPIO_NUM_NC, //interrupt line(input) for SDIO card
    }; //可使用SDSPI_DEVICE_CONFIG_DEFAULT()进行默认初始化

    //--------- 挂载FAT
    ret = esp_vfs_fat_sdspi_mount(MountPoint, //挂载点
                                  &sd_host, //SD host(控制器)
                                  &sd_dev_cfg, //SD device(设备)
                                  &sd_mount_cfg, //挂载配置
                                  &SDCard  //返回SDCard对象
                                ); //TODO: 不使用便捷函数, 拆解为自己的函数
    if(ret != ESP_OK)
    {
        if(ret == ESP_FAIL)
        {
            ESP_LOGI(TAG_SD, "failed to mount filesystem");
        }
        else
        {
            ESP_LOGI(TAG_SD, "failed to init the card (%s)", esp_err_to_name(ret));
        }

        return ret;
    }

    //--------- 输出SDCard信息
    sdmmc_card_print_info(stdout, SDCard);

    return ret;
}

void SDCard_Deinit()
{
    // All done, unmount partition and disable SPI peripheral
    esp_vfs_fat_sdcard_unmount(MountPoint, SDCard);
    ESP_LOGI(TAG_SD, "Card unmounted");

    //deinitialize the bus after all devices are removed
    spi_bus_free(SPIHostID);
}


//================== 获取文件信息 & 打开文件
// description:
//      SDCard_File_Open()要返回ESP_OK才允许继续后续操作
//
// input:
//      file_path, 文件路径, 会自动拼接MountPoint
//      mode, 打开文件模式, 传入FILE_MODE_READ/FILE_MODE_WRITE/FILE_MODE_READ_WRITE
//
// return:
//      0=ESP_OK, 文件打开成功
//      -1=ESP_FAIL, 文件打开失败
//
// esp_err_t SDCard_File_CheckAndOpen(char *file_path, const char *mode)
esp_err_t SDCard_File_Open(char *file_path, const char *mode) //
{
    struct stat sta;
    char full_file_path[50];

    //拼接文件路径
    sprintf(full_file_path, MOUNT_POINT"%s", file_path);
    ESP_LOGI(TAG_SD, "mode=%s, full_path=%s", mode, full_file_path);

    //模式判断 - 读, 需要判断文件是否存在
    // strstr() in <string.h>
    // stat!=0表示没有这个文件
    if((strstr(mode, FILE_MODE_READ) != NULL) || (strstr(mode, FILE_MODE_READ_WRITE) != NULL)) //读模式
    {
        if((stat(full_file_path, &sta) != 0)) //文件不存在
        {
            ESP_LOGI(TAG_SD, "FileOpen - !!! read mode AND no file");
            return ESP_FAIL;
        }
    }
    else if(strstr(mode, FILE_MODE_WRITE) != NULL) //写模式
        ESP_LOGI(TAG_SD, "FileOpen - write mode");
    else //其他模式
    {
        ESP_LOGI(TAG_SD, "FileOpen - !!! other mode");
        return ESP_FAIL;
    }

    //读/写模式 打开文件并判断, File是.c全局变量
    File = fopen(full_file_path, mode);
    if(File == NULL)
    {
        ESP_LOGI(TAG_SD, "FileOpen - open file failed");
        return ESP_FAIL;
    }
    else
    {
        ESP_LOGI(TAG_SD, "FileOpen - open file ok");
        return ESP_OK;
    }
}

//================== 读取文件, 读取一行内容
// description: 
//      //按照sizeof(buf)长度读取字符
//      读取一行内容, 通过反复调用读取整个文件
//
// return:
//      ESP_OK, 未达到文件尾, feof()返回0
//      ESP_FAIL, 达到文件尾, feof()返回非0
//
esp_err_t SDCard_File_Read(char *buf, uint16_t size)
{
    if(feof(File) == 0) //遇到结束返回非0; 未遇到结束返回0
    {
        // memset(file_buf, NULL, sizeof(file_buf));
        fgets(buf, sizeof(buf), File); //可以直接用sizeof而不使用size

        return ESP_OK;
    }

    return ESP_FAIL;
}

//================== 从Img文件中读取长度为len的数据到缓存imgVal
// description:
//      与SDCard_File_Write_Img()对应
//
// input:
//      imgVal, 接收Img数据的指针, 为char类型, 与ePaper_SetImgDataByChar()入参对应
//
// return:
//      ESP_OK, 未到EOF
//      ESP_FAIL, 达到EOF
//
esp_err_t SDCard_File_Read_Img(char *imgVal, uint16_t len)
{
    //读取一行
    fgets(imgVal, len, File); //imgVal是传入的指针, sizeof(imgVal)=4, 需要再传入length
    //判断feof
    if(feof(File) != 0) //遇到结束返回非0; 未遇到结束返回0
    {
        ESP_LOGI(TAG_SD, "end of file");
        return ESP_FAIL;
    }
    //log打印字符
    // ESP_LOGI(TAG_SD, "FileReadImg - %s, %d", imgVal, strlen(imgVal)); //strlen()包含'\n'
    //判断'\n'
    if(imgVal[0] == '\n')
    {
        ESP_LOGI(TAG_SD, "get blank");
        return ESP_FAIL; //读取到'\n', 认为已结束
    }

    return ESP_OK;
}

//================== 将长度为len到imgVal缓存写入文件
// descripiton:
//      将/EPD/photo.txt复制到/EPD/img/中, 并重命名
//
esp_err_t SDCard_File_Write_Img(char *imgVal, uint16_t len)
{
    //检查是否已打开文件File
    if(File == NULL)
    {
        // ESP_LOGI(TAG_SD, "FileWriteImg - empty File!!!");
        return ESP_FAIL;
    }
    
    char tmp[FILE_WRITE_BLOCK_SIZE]; //len最大应该在1000
    tmp[len] = '\0';
    memcpy(tmp, imgVal, len);
    // ESP_LOGI(TAG_SD, "FileWriteImg - %s", tmp);

    fprintf(File, "%s\n", tmp); //按十六进制写入
    // ESP_LOGI(TAG_SD, "FileWriteImg - %s", tmp);

    return ESP_OK;
}

//================== 关闭文件
//
void SDCard_File_Close()
{
    if(File != NULL)
    {
        ESP_LOGI(TAG_SD, "close file");

        fclose(File);
        File = NULL;
    }
}

//==================  保存Photo到文件(实际是将/EPD/photo.txt拷贝到/EPD/img/中)
//
int8_t SDCard_File_Copy_Img()
{
    FILE *dest_file;
    char full_file_path[FILE_PATH_LEN_MAX];
    uint32_t random = esp_random();
    char *buffer;
    uint16_t cnt;

    //设置file path
    sprintf(full_file_path, "%s%s%5ld.txt", MOUNT_POINT, DEFAULT_IMG_DIR, random % 0x0000FFFF);
    //
    ESP_LOGI(TAG_SD, "SDCard_File_Copy_Img: generate file name is %s", full_file_path);

    //创建文件
    dest_file = fopen(full_file_path, FILE_MODE_WRITE);
    //
    if(dest_file == NULL)
    {
        ESP_LOGI(TAG_SD, "SDCard_File_Copy_Img: open file failed");
        return -1;
    }
    else
    {
        buffer = (char *)malloc(sizeof(char) * FILE_WRITE_BLOCK_SIZE);
        cnt = 0;

        while(fgets(buffer, FILE_WRITE_BLOCK_SIZE, File))
        {
            fputs(buffer, dest_file);
            cnt++;

            // ESP_LOGI(TAG_SD, "buffer: %s", buffer);
        }
        free(buffer);
        fclose(dest_file);

        ESP_LOGI(TAG_SD, "SDCard_File_Copy_Img: %d lines copied", cnt);

        return 0;
    }

}

//==================  删除当前选择块对应文件
//
int8_t SDCard_File_Delete_Img()
{
    struct stat sta;
    char file_name[FILE_PATH_LEN_MAX];
    
    //获取文件名
    SDCard_File_GetOnePath(file_name);
    ESP_LOGI(TAG_SD, "try to delete %s", file_name);
    //判断文件存在
    if(stat(file_name, &sta) == 0)
    {
        if(remove(file_name) == 0) //删除Photo
        {
            // ESP_LOGI(TAG_SD, "delete Photo ok");
            // //重置Gui
            // // Load_GuiImg_from_File(NULL);
            // //刷新file list
            // SDCard_File_ListAllNames(DEFAULT_IMG_DIR_PATH);
            // //更新CurrentIdx
            // if(SDCard_File_IsCurrentIdxAtTail() == 1)
            //     Update_GuiImg_to_Select(-1);
            // //刷新Gui
            // Update_GuiImg_from_List();

            return 0;
        }
        else
        {
            ESP_LOGI(TAG_SD, "delete Photo failed..");

            return -1;
        }
    }
    else
    {
        ESP_LOGI(TAG_SD, "file not exist");

        return -1;
    }
        
}


//================== 遍历并记录文件名
// description:
//      通过readdir()遍历指定路径下文件, 
//      将 文件名 放入全局变量DirFileNames[]中, 
//      按照每页 FILE_NUM_IN_PAGE 个文件计算Pages, 以及最后一页文件数 LastPage
//
int8_t SDCard_File_ListAllNames(char *dir)
{
    char full_dir_path[50];

    //拼接路径
    sprintf(full_dir_path, MOUNT_POINT"%s", dir);
    ESP_LOGI(TAG_SD, "list dir: %s", dir);
    
    //打开目录
    DIR *root_dir = opendir(full_dir_path);
    if(root_dir == NULL)
        return -1;
    //遍历目录内文件
    DirFileNum = 0;
    memset(DirFileNames, '\0', sizeof(DirFileNames));
    while(1)
    {
        struct dirent* de = readdir(root_dir); //用于遍历
        if(!de)
            break;
        //保存文件名, 文件数量计数+1
        sprintf(DirFileNames[DirFileNum++], "%s", de->d_name);
        ESP_LOGI(TAG_SD, "file: %s", DirFileNames[DirFileNum-1]); //DEBUG

        if(DirFileNum >= FILE_NUM_MAX)
            break;
    }
    ESP_LOGI(TAG_SD, "find %d files", DirFileNum); //DEBUG
    //关闭目录
    closedir(root_dir);

    //保存文件数量信息 //TODO: ???用DirFileNum == 0判断什么
    LastPage = DirFileNum % FILE_NUM_IN_PAGE;
    Pages = (LastPage == 0) ? (DirFileNum / FILE_NUM_IN_PAGE) : (DirFileNum / FILE_NUM_IN_PAGE) + 1;
    ESP_LOGI(TAG_SD, "now has %d pages, last page has %d files", Pages, LastPage);

    return 0;
}

//================== 返回CurrentPage对应的文件名
//
// param:
//      page, 要读取的page数, 从1开始
//      out_names[][], 返回的文件名数组
// return:
//      file_num, 正常则返回文件数量
//      -1, DirFileNum=0返回-1
//
// Q: 如何实现二维数组传参
// A: a.可以传数组名; b.可以传首地址指针
//
// int8_t SDCard_File_GetNames(uint8_t page, char out_names[FILE_NUM_IN_PAGE][FILE_NAME_LEN_MAX])
int8_t SDCard_File_GetNames(char out_names[FILE_NUM_IN_PAGE][FILE_NAME_LEN_MAX])
{
    //判断是否已遍历过文件
    if(DirFileNum == 0)
        return -1;

    uint8_t i;
    // uint8_t start_idx = (page - 1) * FILE_NUM_IN_PAGE;
    // uint8_t file_num = ((page == Pages) && (LastPage > 0)) ? LastPage : FILE_NUM_IN_PAGE; //取出 LastPage个 或 FILE_NUM_IN_PAGE个 文件名
    // 
    uint8_t start_idx = CurrentPage * FILE_NUM_IN_PAGE;
    uint8_t file_num = ((CurrentPage == (Pages-1)) && (LastPage > 0)) ? LastPage : FILE_NUM_IN_PAGE; //取出 LastPage个 或 FILE_NUM_IN_PAGE个 文件名

    //清空
    memset(out_names, '\0', sizeof(char) * FILE_NUM_IN_PAGE * FILE_NAME_LEN_MAX);
    //拷贝文件名
    for(i = 0; i < file_num; i++)
    {
        sprintf(out_names[i], "%s", DirFileNames[start_idx + i]);
    }

    // //DEBUG
    // for(i = 0; i < FILE_NUM_IN_PAGE; i++)
    // {
    //     ESP_LOGI(TAG_SD, "copy file name:%s", out_names[i]); //调用数组的第一维度
    // }

    // if(CurrentPage == Pages)
    //     CurrentPage = 1; //已取完所有page, 重新循环
    // else
    //     CurrentPage++; //下次将返回下一组Page的文件列表

    return file_num;
}

//================== 返回指定文件完整path
//
void SDCard_File_GetOnePath(char out_name[FILE_PATH_LEN_MAX])
{
    // uint8_t file_idx = (CurrentPage - 1) * FILE_NUM_IN_PAGE + CurrentIdx;
    //
    // sprintf(out_name, "%s", DirFileNames[file_idx]);

    sprintf(out_name, "%s%s", DEFAULT_IMG_DIR, DirFileNames[CurrentIdx]);

}

//================== 更新CurrentPage
//
// void SDCard_File_CurrentPage_Set(int8_t page)
// {
//     if(page == 0)
//         CurrentPage = 0; //复位
//     else
//         CurrentPage += page; //正常迭代

//     //达到第一页Page后, 重置为最后一页
//     if(CurrentPage == -1)
//         CurrentPage = Pages - 1;
//     //达到最后一页Page后, 重置为第一页
//     if(CurrentPage == Pages)
//         CurrentPage = 0;
// }

//================== 返回CurrentPage
//
int8_t SDCard_File_CurrentPage_Get()
{
    // return CurrentIdx / FILE_NUM_IN_PAGE;
    return CurrentPage;
}

//================== 更新CurrentIdx
//
int8_t SDCard_File_CurrentIdx_Set(int8_t idx)
{   
    if(idx == 99)
    {
        CurrentIdx = 0; //复位
        CurrentPage = 0;
    }
    else
        CurrentIdx += idx; //正常迭代

    //达到first Idx后, 重置为last
    if(CurrentIdx == -1) 
    {
        CurrentIdx = DirFileNum - 1;
        CurrentPage = Pages - 1;

        return 1; //需要刷新Gui

        // SDCard_File_CurrentPage_Set(-1); //上翻一Page

        // //更新CurrentIdx
        // if(SDCard_File_CurrentPage_Get() == (Pages - 1)) //最后一页面
        //     CurrentIdx = LastPage - 1;
        // else //其他页面
        //     CurrentIdx = FILE_NUM_IN_PAGE - 1;
    }
    else if(CurrentIdx == DirFileNum)//达到last Idx后, 重置为first
    {
        CurrentIdx = 0;
        CurrentPage = 0;

        return 1; //需要刷新Gui
    }
    else if((CurrentIdx > 0) && (CurrentIdx % FILE_NUM_IN_PAGE) == 0) //跨过Page
    {
        CurrentPage += idx;
        return 1; //需要刷新Gui
    }
    else
        return -1;
    
    // if(CurrentPage == FILE_NUM_IN_PAGE)
    // {
    //     SDCard_File_CurrentPage_Set(1); //下翻一Page

    //     //更新CurrentIdx
    //     if(SDCard_File_CurrentPage_Get() == (Pages - 1))
    //         CurrentIdx = LastPage; //最后一页面
    //     else
    //         CurrentPage = 0;; //其他页面
    // }
    
    return 0;
}

//================== 返回CurrentIdx
//
int8_t SDCard_File_CurrentIdx_Get()
{
    return CurrentIdx;
}

//================== 返回选择点在当前Page的位置
//
int8_t SDCard_File_GetSelectPosition()
{
    return CurrentIdx - CurrentPage * FILE_NUM_IN_PAGE;
}

//================== 返回选择点是否在结尾
//
int8_t SDCard_File_IsCurrentIdxAtTail()
{
    if(CurrentIdx == DirFileNum - 1) //在末尾
        return 1;
    else
        return -1;
}






//================== SDCard测试函数 - 读写文件
//
// fopen参数
//    r, 读取方式打开, 文件必须存在
//    w, 写入方式创建, 文件为空
//    a, 追加方式打开, 文件不存在会创建
//    r+, 可读可写方式打开, 文件必须存在
//    w+, 可读可写方式创建, 文件为空
//    a+, 可读可写方式追加
void SDCard_Test()
{
    ESP_LOGI(TAG_SD, "Start SD Card Test...... in %s", SDCard->cid.name);


    struct stat sta;
    const char *file_path = MOUNT_POINT"/web_files/test.md";

    if(stat(file_path, &sta) == 0) //stat==0则文件存在
    {
        ESP_LOGI(TAG_SD, "GET FILE: test.md");
        //open
        FILE *file = fopen(file_path, "r+");
        if(file == NULL)
        {
            ESP_LOGI(TAG_SD, "failed to open file!!!");
            return;
        }
        //read
        char file_buf[1024];
        while(feof(file) == 0) //遇到结束返回非0; 未遇到结束返回0
        {
            memset(file_buf, '\0', sizeof(file_buf));
            fgets(file_buf, sizeof(file_buf), file);
            ESP_LOGI(TAG_SD, "%s", file_buf);
        }
        //write
        fprintf(file, "Aloha World!\n");
        //close
        fclose(file);
    }
    else
    {
        ESP_LOGI(TAG_SD, "NO FILE: test.md, will create one");
        //open
        FILE *file = fopen(file_path, "w");
        if(file == NULL)
        {
            ESP_LOGI(TAG_SD, "failed to create file!!!");
            return;
        }
        //write
        fprintf(file, "!!!Aloha World!!!\n");
        //close
        fclose(file);
    }
    

    ESP_LOGI(TAG_SD, "End SD Card Test......");
}


//================== SDCard测试函数 - 遍历文件
//
// #define DIR_NUM_MAX     10
//
void SDCard_Test_2()
{
    ESP_LOGI(TAG_SD, "open root dir: %s", MountPoint);
    DIR *root_dir = opendir(MountPoint);

    int cnt = 0;
    // const char* names[DIR_NUM_MAX];

    while(1)
    {
        struct dirent* de = readdir(root_dir); //用于遍历
        if(!de)
        {
            break;
        }
        //
        printf("\t%s\n", de->d_name);
        // sprintf(names[cnt], "%s", de->d_name);
        sprintf(DirFileNames[DirFileNum++], "%s", de->d_name);
        // names[cnt] = de->d_name;
        ++cnt;

        printf("read cnt: %d\r\n", cnt);
    }

    closedir(root_dir);


    //尝试打开文件
    uint16_t i;
    for(i = 0; i < cnt; i++)
        printf("\t%s\n", DirFileNames[i]);
    //
    char path[80];
    sprintf(path, MOUNT_POINT"/%s", DirFileNames[1]); //拼接文件路径
    //
    struct stat sta;
    if(stat(path, &sta) == 0) //stat==0则文件存在
    {
        ESP_LOGI(TAG_SD, "GET FILE: %s", path);
        //open
        FILE *file = fopen(path, "r+");
        if(file == NULL)
        {
            ESP_LOGI(TAG_SD, "failed to open file!!!");
            return;
        }
        //read
        char file_buf[1024];
        while(feof(file) == 0) //遇到结束返回非0; 未遇到结束返回0
        {
            memset(file_buf, '\0', sizeof(file_buf));
            fgets(file_buf, sizeof(file_buf), file);
            ESP_LOGI(TAG_SD, "%s", file_buf);
        }
        //write
        // fprintf(file, "Aloha World!\n");
        //close
        fclose(file);
    }
    else
        ESP_LOGI(TAG_SD, "NOT GET FILE: %s", path);

}


//================== SDCard测试函数 - 获取文件名并打印
//
void SDCard_Test_3()
{
    // char tmp_names[10][50];
    // uint8_t i;
    // SDCard_File_ListAllNames(MOUNT_POINT"/web_files");
    // SDCard_File_GetNames(1, tmp_names);
    // for(i = 0; i < 10; i++)
    // {
    //     ESP_LOGI(TAG_MAIN, "\t%s", tmp_names[i]);
    // }
}




